/**
 * @file StreamPipe.c
 *
 * This file implements the StreamPipe interface.
 *
 * @author Luc Fourestier
 * @date 2009
 *
 * @page SPI The StreamPipe
 *
 * The StreamPipe module creates/manages the "triplettes" in order to create/manage stream pipes.
 *
 * @section SPI_Conv The naming convention
 *
 * @li We call "session", the time or duration during when a stream pipe is alive.
 * @li We call "recording session", a session aimed to store the incoming network stream into a file.
 * @li We call "playing session", a session aimed to play a network stream out of a file.
 *
 * @section SPI_Arch The Architecture
 *
 * The StreamPipe module provides creation and deletion functions as well as list and print functions.
 * These functions will manage the StreamPipes. To do so, a double linked-list is maintained containing
 * all the created StreamPipes.
 *
 * @section SPI_Design The design
 *
 * TO BE COMPLETED
 *
 */

#include "StreamPipe.h"

#include "TS.h"

#include <stdlib.h>
#include <string.h>

// Defines
#define MAGIC 0x55
#define MAX_CB_SIZE 5000

// List management
SPI_StreamPipe_t * SPIListHead = NULL;

/**
 * Allocate the streamPipe structure, create the SA, SR and SP necessary
 * to create the streamPipe. Remember the running StreamPipe in a list.
 *
 * In case of error and if error is not SPI_ERROR_CANNOT_ALLOCATE, you must call SPI_Delete().
 *
 * @param SPI_p (out): The pointer where to return the allocated StreamPipe structure (can be NULL).
 * @param Options (in): The creation options.
 * @return Errors.
 */
int SPI_Create(SPI_StreamPipe_t ** SPI_p, SPI_Option_t Options)
{
	int RetVal = OK;
	SPI_StreamPipe_t * SPI;
	unsigned int i = 0;

	SA_Option_t SAOptions;
	SR_Option_t SROptions;
	SP_Option_t SPOptions;

	// Create the SPI
	SPI = (SPI_StreamPipe_t*) malloc(sizeof(SPI_StreamPipe_t));
	if (SPI == NULL)
	{
		return(SPI_ERROR_CANNOT_ALLOCATE);
	}
	bzero((void*)SPI, sizeof(SPI_StreamPipe_t));

	// remember options
	SPI->Options = Options;

	// Create the triplette according to the session type
	switch (Options.SessionType)
	{
	case Record:
		// Start the StreamAnalyzer
		bzero(&SAOptions, sizeof(SAOptions));
		SAOptions.OutputType = File;
#ifdef DEBUG_JITTER_FILE
		strncpy(SAOptions.JitterFileName, DEFAULT_JITTER_NAME, SA_MAX_NAME_LENGTH);
#endif
		SAOptions.ReferenceTime = Options.ReferenceTime;
		SAOptions.MaxBufferNumber = Options.MaxCBSize;
		SAOptions.MaxBufferSize = MAX_CHUNK_SIZE;

		RetVal = SA_Create(&SPI->SA, SAOptions);
		if (RetVal != OK)
		{
			return(SPI_ERROR_CANNOT_CREATE_SA);
		}

		// Start the StreamReader
		bzero(&SROptions, sizeof(SROptions));
		SROptions.InputType = Udp;
		strncpy(SROptions.RcvAddress, Options.Address, IP_ADDR_LENGTH);
		SROptions.RcvPort = Options.Port;
		strncpy(SROptions.BindAddress, Options.OwnAddress, IP_ADDR_LENGTH);
		SROptions.CB = SPI->SA->CircBuffer;
		SROptions.ReferenceTime = Options.ReferenceTime;

		RetVal = SR_Create(&SPI->SR, SROptions);
		if (RetVal != OK)
		{
			return(SPI_ERROR_CANNOT_CREATE_SR);
		}

		// Start the StreamPlayer
		bzero(&SPOptions, sizeof(SPOptions));
		SPOptions.OutputType = File;
		strncpy(SPOptions.FileName, Options.FileName[0], SP_MAX_NAME_LENGTH);
		SPOptions.CB = SPI->SA->CircBuffer;
		SPOptions.ReferenceTime = Options.ReferenceTime;

		RetVal = SP_Create(&SPI->SP, SPOptions);
		if (RetVal != OK)
		{
			return(SPI_ERROR_CANNOT_CREATE_SP);
		}

		break;

	case Playback:
		// Start the StreamAnalyzer
		bzero(&SAOptions, sizeof(SAOptions));
#ifdef DEBUG_JITTER_FILE
		strcpy(SAOptions.JitterFileName, DEFAULT_JITTER_NAME);
#endif
		SAOptions.OutputType = Udp;
		SAOptions.MaxBufferNumber = Options.MaxCBSize;
		SAOptions.MaxBufferSize = MAX_CHUNK_SIZE;
		SAOptions.ReferenceTime = Options.ReferenceTime;

		RetVal = SA_Create(&SPI->SA, SAOptions);
		if (RetVal != OK)
		{
			return(SPI_ERROR_CANNOT_CREATE_SA);
		}

		// Start the StreamReader
		bzero(&SROptions, sizeof(SROptions));
		SROptions.InputType = File;
		i = 0;
		while ((Options.FileName[i][0] != 0) && (i < SR_MAX_FILE))
		{
			strncpy(SROptions.FileName[i], Options.FileName[i], SR_MAX_NAME_LENGTH);
			i++;
		}
		SROptions.CB = SPI->SA->CircBuffer;
		SROptions.ReferenceTime = Options.ReferenceTime;

		RetVal = SR_Create(&SPI->SR, SROptions);
		if (RetVal != OK)
		{
			return(SPI_ERROR_CANNOT_CREATE_SR);
		}

		// Start the StreamPlayer
		bzero(&SPOptions, sizeof(SPOptions));
		SPOptions.OutputType = Udp;
		strncpy(SPOptions.SendAddress, Options.Address, IP_ADDR_LENGTH);
		SPOptions.SendPort = Options.Port;
		SPOptions.CB = SPI->SA->CircBuffer;
		SPOptions.ReferenceTime = Options.ReferenceTime;

		RetVal = SP_Create(&SPI->SP, SPOptions);
		if (RetVal != OK)
		{
			return(SPI_ERROR_CANNOT_CREATE_SP);
		}

		break;

		default:
			RetVal = ERROR;
			break;
	}

	// Add the structure into the list
	if (SPIListHead != NULL)
	{
		SPIListHead->Previous = SPI;
	}
	SPI->Next = SPIListHead;
	SPI->Previous = NULL;
	SPIListHead = SPI;

	SPI->Magic = MAGIC;

	if (SPI_p != NULL)
	{
		// Return the structure
		*SPI_p = SPI;
	}

	return(RetVal);
}

/**
 * Delete the StreamPipe structure. Remove it from the list.
 *
 * @param SPI (in): The StreamPipe to delete.
 * @return Errors
 */
int SPI_Delete(SPI_StreamPipe_t * SPI)
{
	int RetVal = OK;

	if ((SPI != NULL) && (SPI->Magic == MAGIC))
	{
	    SPI->Magic = 0;

		// Delete the triplette
		SP_Delete(SPI->SP);
	    SR_Delete(SPI->SR);
		SA_Delete(SPI->SA);

		// Remove SPI from the list
		if (SPI->Previous != NULL)
		{
			SPI->Previous->Next = SPI->Next;
		}
		else // we are at the head
		{
			SPIListHead = SPI->Next;
		}
		if (SPI->Next != NULL)
		{
			SPI->Next->Previous = SPI->Previous;
		}

		// Free the StreamPipe
		free(SPI);
	}
	else
	{
		RetVal = SPI_ERROR_BAD_PARAM;
	}

	return(RetVal);
}

/**
 * Delete the StreamPipe structure. Remove it from the list.
 *
 * @param SessionID (in): The session ID of the StreamPipe structure to delete.
 * @return Errors
 */
int SPI_DeleteID(char * SessionID)
{
	int RetVal = OK;

	if (SessionID != NULL)
	{
		SPI_StreamPipe_t * SPI = NULL;
		int Error = OK;

		Error = SPI_Search(SessionID, &SPI);
		if ((Error == OK) && (SPI != NULL))
		{
			RetVal = SPI_Delete(SPI);
		}
		else
		{
			return(ERROR);
		}
	}
	else
	{
		RetVal = SPI_ERROR_BAD_PARAM;
	}

	return(RetVal);
}

/**
 * Delete the complete list of SPI.
 *
 * @return Errors
 */
int SPI_DeleteList(void)
{
	int RetVal = OK;
	SPI_StreamPipe_t * SPI = SPIListHead;

	while (SPI != NULL)
	{
		SPI_Delete(SPI);
		SPI = SPIListHead; // Because SPI_Delete remove the SPI from the list and free it.
	}

	return(RetVal);
}


/**
 * Read the list of open StreamPipe and list them into a table.
 *
 * @param SPITable (in/out): The pointer where to store the table of StreamPipe (The user MUST allocate it)
 * @param Size_p (in/out): As input, the size (number of element) of the table, as output, the actual size written back.
 * @return Errors
 */
int SPI_List(SPI_StreamPipe_t ** SPITable, unsigned int * Size_p)
{
	int RetVal = OK;

	if ((SPITable != NULL) && (Size_p != NULL) && (*Size_p > 0))
	{
		int i = 0;
		SPI_StreamPipe_t * SPI = SPIListHead;

		while ((i < *Size_p) && (SPI != NULL))
		{
			SPITable[i] = SPI;
			SPI = SPI->Next;
			i++;
		}

		*Size_p = i;

		if (i == 0)
		{
			RetVal = SPI_ERROR_EMPTY_LIST;
		}
	}
	else
	{
		RetVal = SPI_ERROR_BAD_PARAM;
	}

	return(RetVal);
}

/**
 * Print the StreamPipe info given by the StreamPipe structure SPI_StreamPipe_t.
 *
 * @param ID (in): The StreamPipe info structure
 * @param Level (in): The level of details
 * @param Output (in): Where to print (File or stdout...)
 * @return Errors
 */
int SPI_Print(SPI_StreamPipe_t * SPI, int Level, FILE * Output)
{
	int RetVal = OK;
	unsigned int i = 0;

	if ((SPI == NULL) || (SPI->Magic != MAGIC) || (Output == NULL))
	{
		return(SPI_ERROR_BAD_PARAM);
	}

	if (Level >= PRINT_LEVEL_INTERMEDIATE)
	{
		fprintf(Output, "-----------------------------------------------\n");
	}

	fprintf(Output, "%s: ", SPI->Options.SessionID);
	if (SPI->Options.SessionType == Record)
	{
		fprintf(Output, "Record ");
		fprintf(Output, "%s:%i -> %s", SPI->Options.Address, SPI->Options.Port, SPI->Options.FileName[0]);
	}
	else if (SPI->Options.SessionType == Playback)
	{
		fprintf(Output, "Playback ");
		if (SPI->SR->CurrentFile == 0)
		{
			fprintf(Output, "*");
		}
		fprintf(Output, "%s", SPI->Options.FileName[0]);
		i = 1;
		while ((SPI->Options.FileName[i][0] != 0) && (i < SR_MAX_FILE))
		{
			fprintf(Output, ",");
			if (SPI->SR->CurrentFile == i)
			{
				fprintf(Output, "*");
			}
			fprintf(Output, "%s", SPI->Options.FileName[i]);
			i++;
		}
		fprintf(Output, " -> %s:%i", SPI->Options.Address, SPI->Options.Port);
	}

	fprintf(Output, ", duration=%.1fs", SPI->SA->Duration);
	if (SPI->SR->ThreadLoop == 1)
	{
		fprintf(Output, ", Ongoing\n");
	}
	else
	{
		fprintf(Output, ", Stopped\n");
	}

	if (Level >= PRINT_LEVEL_INTERMEDIATE)
	{
		fprintf(Output, "-----------------------------------------------\n");
	}

	if (Level >= PRINT_LEVEL_INTERNALS)
	{
		fprintf(Output, "OwnAddress: %s\n", SPI->Options.OwnAddress);
		fprintf(Output, "MaxCBSize: %i\n", SPI->Options.MaxCBSize);
		fprintf(Output, "ReferenceTime: %i-%i\n", (int)SPI->Options.ReferenceTime.tv_sec, (int)SPI->Options.ReferenceTime.tv_usec);
	}

	// Internals status
	if (Level >= PRINT_LEVEL_INTERMEDIATE)
	{
		fprintf(Output, "\n");
		RetVal = SR_Print(SPI->SR, "SR", Level, Output);
		RetVal = SA_Print(SPI->SA, "SA", Level, Output);
		RetVal = SP_Print(SPI->SP, "SP", Level, Output);
	}


	if (RetVal != OK)
	{
		RetVal = ERROR;
	}

	return(RetVal);
}

/**
 * Print the StreamPipe info given by the ID.
 *
 * @param SessionID (in): The StreamPipe session ID
 * @param Level (in): The level of details
 * @param Output (in): Where to print (File or stdout...)
 * @return Errors
 */
int SPI_PrintID(char * SessionID, int Level, FILE * Output)
{
	int RetVal = OK;

	if ((SessionID != NULL) && (Output != NULL))
	{
		SPI_StreamPipe_t * SPI = NULL;
		int Error = OK;

		Error = SPI_Search(SessionID, &SPI);
		if ((Error == OK) && (SPI != NULL))
		{
			RetVal = SPI_Print(SPI, Level, Output);
		}
		else
		{
			RetVal = SPI_ERROR_EMPTY_LIST;
		}
	}
	else
	{
		RetVal = SPI_ERROR_BAD_PARAM;
	}

	return(RetVal);
}


/**
 * Print the complete list of SPI.
 *
 * @param Level (in): The level of details
 * @param Output (in): Where to print (File or stdout...)
 * @return Errors
 */
int SPI_PrintList(int Level, FILE * Output)
{
	int RetVal = OK;

	if (Output != NULL)
	{
		if (SPIListHead != NULL)
		{
			SPI_StreamPipe_t * SPI = SPIListHead;

			while (SPI != NULL)
			{
				SPI_Print(SPI, Level, Output);
				SPI = SPI->Next;
			}
		}
		else
		{
			RetVal = SPI_ERROR_EMPTY_LIST;
		}
	}
	else
	{
		RetVal = SPI_ERROR_BAD_PARAM;
	}

	return(RetVal);
}


/**
 * Search the SPI specified by the session ID in the list.
 *
 * @param SessionID (in): The session ID of the SPI to search
 * @param SPI_p (out): The found SPI, or NULL.
 * @return Errors
 */
int SPI_Search(char * SessionID, SPI_StreamPipe_t ** SPI_p)
{
	int RetVal = OK;

	if (SPI_p != NULL)
	{
		SPI_StreamPipe_t * CurrentSPI = NULL;

		if (SPIListHead != NULL)
		{
			CurrentSPI = SPIListHead;
			do
			{
				if (strncmp(CurrentSPI->Options.SessionID, SessionID, SPI_MAX_NAME_LENGTH) == 0)
				{
					break;
				}
				CurrentSPI = CurrentSPI->Next;
			} while (CurrentSPI != NULL);
		}
		else
		{
			RetVal = SPI_ERROR_EMPTY_LIST;
		}

		*SPI_p = CurrentSPI;
	}
	else
	{
		RetVal = SPI_ERROR_BAD_PARAM;
	}

	return(RetVal);
}

